Procesos e Hilos

Procesos

Un proceso es un programa en ejecución. Se suele decir que el programa es la entidad pasiva y el proceso es la activa.

Un proceso es la unidad de ejecución más pequeña planificable.

Un proceso está compuesto por:

Text: Es el código escrito y fijo del proceso.

Data: Es aquella que envuelve a todas las variables globales, de tamaño fijo.

Heap: En el heap se encuentra la memoria dinámica, Una forma de hacer crecer al heap es a través de la función malloc(), a la hora de pedir memoria.

Stack (o pila): Dentro de esta se encuentran las variables locales, los retornos de funciones, y los parámetros de funciones (que terminan siendo variables locales). El stack es dinámico, de tamaño variable. El famoso stackoverflow se genera cuando la memoria dada a dicha stack se supera, es decir, se queda sin memoria.

Estados de un proceso

Para cada estado del proceso vamos a decir que se encuentra en memoria, a pesar de que solo uno se esté ejecutando (running).

Ejemplo de un proceso que estaba en running y paso a waiting, es cuando el mismo necesita abrir un archivo, entonces se dispara una operación de I/O y el proceso que se estaba ejecutando pasa a estar en espera, hasta que termine la rutina y vuelva a estar en ready.

Es importante saber que la cantidad de procesos cargados en memoria tiene un límite, a eso se le llama grado de multiprogramación. Cada vez que un proceso salta de new a ready, significa que está en memoria, y está ocupando un puesto de multiprogramación.

Por otro lado, al límite de n procesos corriendo al mismo tiempo (en el estado running) se lo llama grado de multiprocesamiento, limitado a su vez por la cantidad de núcleos (cores) del procesador.

Planificaciones

Si le hacemos un zoom a los 3 estados del medio (ready, running, y waiting o blocked), vamos a encontrarnos con otros dos estados más: suspendido bloqueado y suspendido listo.

Dentro de los tipos de planificaciones, vamos a decir que tanto los de largo plazo como los de mediano plazo inciden en el grado de multiprogramación, ya que los de mediano plazo son los encargados de suspender o desuspender los procesos, en el caso que por ejemplo, pase lo ocurrido en el ejemplo de arriba.

Por otro lado, el tipo de planificación de extra largo plazo, se relaciona con acciones como configurar el grado de multiprogramación, entre otras.

La planificación de extra largo plazo es la que menos se utiliza, mientras que la de corto plazo es en la que más el sistema y nosotros nos vamos a enfocar, con el fin de que se puedan ejecutar la mayor cantidad de procesos posibles y tener el menor overhead posible también.

PCB (Process Control Block)

Contiene información asociada con cada proceso:

● ID

● Estado del proceso

● Contador de programa (PC o IP)

● Registros de la CPU

● Información de planificación de CPU

● Información de gestión de memoria

● Información contable

● Información de estado de E/S

● Punteros

Se podría decir que el sistema operativo usa de índice al PCB, necesita ingresar al mismo para, por ejemplo, ingresar al stack o al heap del proceso, mediante un puntero.

Cambio de contexto

El contexto es todo lo que necesita el sistema operativo para ejecutar un proceso.

Si yo tengo dos procesos, y cambio del proceso 1 al 2, lo que debe hacer el sistema es guardar el contexto (IP, PSW, etc.) del proceso 1 para saber en dónde estoy parado y dejarlo consistente, y así poder cargar el contexto del 2do proceso. Una vez que necesite cambiar del 2do proceso al 1ro, ya voy a tener el contexto guardado para saber volver a donde estaba.

El tiempo que dura un cambio de contexto es un gasto extra, es overhead, el sistema no hace nada útil durante el cambio y esto se debe minimizar al máximo. Este tiempo depende del procesador, y lo que puede hacer este para minimizar dicho overhead es, por ejemplo, si yo sé que después de atender x interrupción, si o si voy a volver al proceso que estaba, lo guardo en el stack en vez de guardarlo en el PCB, ya que es más rápido.

El cambio de contexto puede realizarse para cambiar de proceso, pero también para atender una interrupción (rutina del Kernel), permitiendo que el proceso original continúe (con su contexto previamente guardado) luego de atender dicha interrupción.

Si hubo un cambio de proceso => Hubo dos cambios de contexto (en verdad es uno solo, se guarda el ctx de un proceso y se restaura el del otro)

Si hubo dos cambios de contexto => No necesariamente hubo un cambio de proceso (Se puede elegir al mismo proceso, ejecutar una syscall, atender una interrupción)

Si hubo un cambio de modo => Hubo un cambio de contexto (pasó de ejecutar un proceso de usuario a ejecutar el SO o viceversa)

Si hubo un cambio de contexto => No necesariamente hubo un cambio de modo (puede ocurrir una interrupción cuando ya estoy atendiendo una)

Hilos

La realidad es que la mínima unidad de utilización de la CPU no es un proceso, sino que es un hilo.

● Un hilo consiste en un juego de registros y un espacio de pila. Es también conocido como proceso ligero.

● Comparte el código, los datos y los recursos (files, heap, etc) con sus hilos pares (o hilos hermanos).

● Un proceso está formado por uno o más hilos de ejecución.

● Permiten paralelismo dentro de un proceso o aplicación.

● Al compartir recursos, pueden comunicarse sin usar ningún mecanismo de comunicación inter-proceso del SO, su comunicación es más rápida que la comunicación entre procesos, ya que la memoria compartida se los permite.

● No hay protección entre hilos. Un hilo puede escribir en la pila de otro hilo del mismo proceso.

Los hilos no comparten ni sus registros ni sus stacks entre ellos.

El TCB (Thread Control Block) es lo mínimo que necesito para administrar hilos.

Diferencias con procesos

● Permiten la comunicación privada entre varios hilos del mismo proceso, sin intervención del S.O.

Mayor eficiencia en el cambio de un hilo a otro, que de un Proceso a otro, debido a que solo voy a tener que cambiar dos estructuras, los registros y la stack.

Mayor eficiencia en la creación de un hilo que en la creación de un Proceso Hijo, ya que solo voy a tener que crear dos estructuras, los registros y la stack.

● Un Proceso Multihilo puede recuperarse de la “muerte” de un Hilo, pues conoce los efectos de esta, y toma su espacio de memoria (excepto para el hilo main).

● Cuando un proceso “muere”, generalmente todos sus hilos también pues los recursos del proceso son tomados por el Sistema Operativo.

KLT – ULT

● Los KLTs son llamados “Hilos a nivel de Kernel”. El SO conoce de su existencia y controla su ejecución.

● Los ULTs son llamados “Hilos a nivel de usuario”. El SO NO conoce su existencia debido a que su gestión es realizada por bibliotecas en modo usuario.

● Tanto los KLTs como los ULTs corren en modo usuario.

● Un KLT puede tener muchos ULTs dentro, pero no viceversa.

● Características de los ULTs:

● Realizan el cambio de contexto aún más rápidamente.

● Cuando uno de ellos realiza una operación bloqueante (ej. E/S), sus hilos pares no pueden continuar, porque para el sistema operativo el que pidió que se realice esa operación es el KLT, y no el ULT, porque no lo conoce, por ende se bloquean todos los demás ULTs.

● No permiten el paralelismo entre hilos pares.

Procesos Hijos

Un proceso puede crear otro proceso, iniciando una tarea mientras termina otra.

● Una forma de que un proceso padre pueda crear un proceso hijo a su “imagen y semejanza” es a través de la función fork ().

● El proceso init vendría a ser el proceso padre de todos, es el que se inicia cuando prendemos la CPU.

○ Jerarquía entre Procesos:

■ Padre e Hijo se pueden ejecutar concurrentemente (quizás en paralelo).

■ El Padre hace su trabajo y debe esperar a que el Hijo termine para terminar.

● Padre e Hijo pueden compartir todos los recursos, una parte de ellos, o ninguno.

● La estructura de memoria de un Proceso Hijo es un duplicado de la estructura del Padre (espacio de direcciones).

Creación de procesos (función fork)

Creación de hilos

Cuando yo creo hilos, no solo creo los TCBs sino que también los registros y la stack de cada hilo, ya que los TCBs tienen el PUNTERO a los registros y la stack. Al igual que el PCB, no crea dichas estructuras, sino que apunta a ellas.

Terminación de procesos

● La última operación de un proceso es una llamada al SO indicando que lo elimine (exit)

● Se envía al padre información de salida (via wait)

● Los recursos usados por el proceso son liberados

● Un proceso padre puede terminar la ejecución de sus hijos (abort)

● El hijo se ha excedido en el uso de recursos asignados

● La tarea que realiza el hijo no es ya necesaria

● El padre va a terminar

● Algunos SOs no permiten que un hijo siga si su padre termina. Todos los hijos son terminados – terminación en cascada

● Otros transfieren el hijo al padre del padre

Tip: Si voy a finalizar un proceso mediante una señal, usar SIEMPRE SIGTERM, NUNCA SIGKILL, si usas sigkill estás matando un pingüino.